﻿namespace Game500.Server

open Microsoft.Xna.Framework

type internal Logic(remove_obj : SceneObject -> unit) =
    let mutable statics : SceneObject list = []
    let mutable bullets : Bullet list = []
    let mutable planes : PlaneObject list = []
    let mutable shots : int list = []
    let mutable exploded : int list = []
    let mutable parameters = new Parameters()
    let mutable width = 0.0f
    let mutable height = 0.0f

    member o.Width
        with get () = width
        and set w = width <- w
    member o.Height
        with get () = height
        and set h = height <- h

    member o.Statics
        with get() = statics
        and set s = statics <- s
    member o.Bullets with get() = bullets
    member o.Planes with get() = planes
    member o.Parameters
        with get() = parameters
        and set(p) = parameters <- p
    member o.Shots
        with get () = shots
        and set s = shots <- s
    member o.Exploded
        with get () = exploded
        and set e = exploded <- e

    member o.Explode (p : PlaneObject) =
        exploded <- p.ID :: exploded
        p.Explode parameters.RespawnTimeout
    member o.Hit (p : PlaneObject) (b : Bullet) =
        if p.Hit parameters.RespawnTimeout then o.Explode p; b.Plane.Frag <- b.Plane.Frag + 1

    member o.UpdateCollisions() =
        let seg_seg_isn ax1 ay1 ax2 ay2 bx1 by1 bx2 by2 =
            let v1 = (bx2-bx1)*(ay1-by1)-(by2-by1)*(ax1-bx1)
            let v2 = (bx2-bx1)*(ay2-by1)-(by2-by1)*(ax2-bx1)
            let v3 = (ax2-ax1)*(by1-ay1)-(ay2-ay1)*(bx1-ax1)
            let v4 = (ax2-ax1)*(by2-ay1)-(ay2-ay1)*(bx2-ax1)
            v1*v2 < 0.0f && v3*v4 < 0.0f
        let intersections (o1 : SceneObject) (o2 : SceneObject) =
            let ff x1 y1 x2 y2 (ps2 : seq<Vector2>) (p2 : Vector3) =
                let mutable b = false
                let i2 = ps2.GetEnumerator()
                ignore (i2.MoveNext())
                let mutable prev2 = i2.Current
                while i2.MoveNext() && not b do
                    b <- seg_seg_isn x1 y1 x2 y2 (prev2.X + p2.X) (prev2.Y + p2.Y) (i2.Current.X + p2.X) (i2.Current.Y + p2.Y)
                    prev2 <- i2.Current
                if not b then b <- seg_seg_isn x1 y1 x2 y2 (prev2.X + p2.X) (prev2.Y + p2.Y)
                                ((Seq.head ps2).X + p2.X) ((Seq.head ps2).Y + p2.Y)
                b
            let poly_circle p1 (ps : seq<Vector2>) (p2 : Vector3) r =
                let mutable b = false
                b <- ff 0.0f p2.Y p2.X p2.Y ps p1 && ff width p2.Y p2.X p2.Y ps p1
                if not b then
                    let i = ps.GetEnumerator()
                    while i.MoveNext() && not b do
                        b <- (new Vector3(i.Current.X, i.Current.Y, 0.0f) + p1 - p2).Length() < r
                b
            match o1.Shape with
            | Circle r1 ->
                match o2.Shape with
                | Circle r2 -> (o1.Position - o2.Position).Length() < r1 + r2
                | Polygon (c2, r2, ps) -> (o1.Position - (o2.Position + c2)).Length() < r1 + r2 &&
                                            poly_circle o2.Position ps o1.Position r1
            | Polygon (c1, r1, ps1) ->
                match o2.Shape with
                | Circle r2 -> (o1.Position + c1 - o2.Position).Length() < r1 + r2 &&
                                poly_circle o1.Position ps1 o2.Position r2
                | Polygon (c2, r2, ps2) ->
                    if (o1.Position + c1 - (o2.Position + c2)).Length() < r1 + r2 then
                        let i1 = ps1.GetEnumerator()
                        ignore (i1.MoveNext())
                        let mutable prev1 = i1.Current
                        let mutable b = false
                        while i1.MoveNext() && not b do
                            b <- ff (prev1.X + o1.Position.X) (prev1.Y + o1.Position.Y)
                                (i1.Current.X + o1.Position.X) (i1.Current.Y + o1.Position.Y) ps2 o2.Position
                            prev1 <- i1.Current
                        if not b then b <- ff (prev1.X + o1.Position.X) (prev1.Y + o1.Position.Y) ((ps1 |> Seq.head).X + o1.Position.X)
                                        ((ps1 |> Seq.head).Y + o1.Position.Y) ps2 o2.Position
                        b
                        else false
        let rec collidePlanes (ps : PlaneObject list) =
                match ps with
                | [] -> ()
                | p1 :: ps ->
                    if p1.Visible then
                        for p2 in ps do if p2.Visible && intersections p1 p2 then o.Explode p1; o.Explode p2
                    collidePlanes ps
        collidePlanes planes
        for p in planes do
          if p.Visible then
            for b in bullets do
                if b.Plane <> p && intersections p b then o.Hit p b
            for s in statics do
                if intersections p s then o.Explode p
            let thr = parameters.LandingRotationThreshold
            let rot = if p.Flipped
                        then p.Rotation > MathHelper.Pi + thr || p.Rotation < MathHelper.Pi - thr
                        else p.Rotation > thr && p.Rotation < MathHelper.TwoPi - thr
            if p.Position.Y <= 0.5f && (abs p.Speed.Y > parameters.LandingSpeedThreshold || rot) then o.Explode p
        for b in bullets do
            for s in statics do
                if intersections b s then b.Kill()

    member internal o.Simulate(time : float32) =
        let updatePos (p : Vector3) =
            let mutable pos = p
            if pos.X > width then pos.X <- 0.f
            if pos.X < 0.0f then pos.X <- width
            pos
        for o in planes do
          if o.Visible then
            o.Tick(time)

            let speed2 = o.Speed.Length() * o.Speed.Length()
            let norm_speed = Vector3.Normalize(o.Speed)
            let rot = new Vector3(cos(o.Rotation), -sin(o.Rotation), 0.0f)
            let lift = Vector3.Cross(Vector3.UnitZ, rot) * parameters.Planes.[0].Lift * speed2 * if o.Flipped then -1.0f else 1.0f
            let mutable thurst = rot * o.IncrSpeed
            let friction = norm_speed * (parameters.Planes.[0].Air2 * speed2 + parameters.Planes.[0].Air * o.Speed.Length())
            let gravity = if o.Position.Y <= 0.0f then Vector3.Zero else - Vector3.UnitY * parameters.Gravity * parameters.Planes.[0].Mass
            
            if o.Position.Y > (float32 height) * 0.9f then thurst <- thurst * (1.0f - (float32 o.Position.Y) / (float32 height))
            
            let di = (thurst - friction + lift + gravity) / parameters.Planes.[0].Mass
                    
            o.Speed <- o.Speed + di * time / parameters.Distortion
            o.Speed <- if o.Speed.Length() > parameters.MaxSpeed then o.Speed / o.Speed.Length() * parameters.MaxSpeed else o.Speed
            
            if o.Position.Y <= 0.0f && o.Speed.Y < 0.f then o.Speed <- new Vector3(o.Speed.X, 0.f, o.Speed.Z)
            let mutable pos = o.Position + o.Speed * time
            if pos.Y > height then pos.Y <- height
            if pos.Y < 0.0f then pos.Y <- 0.0f
            o.Position <- updatePos pos
            o.IncrSpeed <- 0.f

        let rec updateBullets(bs : Bullet list) =
            match bs with
            | [] -> []
            | b :: bs ->
                let pos = Vector3.Multiply(b.Speed, time)
                b.Position <- updatePos (Vector3.Add(b.Position, pos))
                b.Tick(time)
                let bs' = updateBullets(bs)
                if b.Dead() || b.Position.Y < -5.0f then remove_obj b; bs' else b :: bs'
        bullets <- updateBullets(bullets)

        o.UpdateCollisions()

    member internal o.MakePolygon(ps : Vector2 seq) =
        let mutable left = (Seq.head ps).X
        let mutable right = (Seq.head ps).X
        let mutable top = (Seq.head ps).Y
        let mutable bottom = (Seq.head ps).Y
        for p in ps do
            if left > p.X then left <- p.X
            if right < p.X then right <- p.X
            if top > p.Y then top <- p.Y
            if bottom < p.Y then bottom <- p.Y
        let c = new Vector2(right - left, bottom - top)
        let mutable r = (Seq.head ps - c).Length()
        for p in ps do
            if (p - c).Length() > r then r <- (p - c).Length()
        Polygon (new Vector3(c.X, c.Y, 0.0f), r, ps)

    member internal o.MakePlane(pos : Vector3, rot : float32, flipped : bool) =
        let mutable ps = []
        for s in parameters.Planes.[0].Shape do
            ps <- new Vector2(s.X, s.Y) :: ps
        let p = new PlaneObject(pos, rot, flipped, o.MakePolygon ps)
        p.Position <- pos
        p.Rotation <- rot
        planes <- p :: planes
        p

    member internal o.RemovePlane(plane : PlaneObject) =
        let rec remove(ps) =
            match ps with
            | [] -> []
            | p :: ps -> if p = plane then ps else p :: remove(ps)
        remove_obj plane
        planes <- remove(planes)

    member internal o.MakeBullet(obj : PlaneObject) =
        if obj.Visible && obj.CanShoot() then
            obj.NextShotIn(parameters.BulletFreq)
            let speed = parameters.BulletSpeed + obj.Speed.Length()
            let b = new Bullet(parameters.BulletDistance / speed, Circle parameters.BulletRadius, obj)
            let rot = new Vector3(cos(obj.Rotation), -sin(obj.Rotation), 0.0f)
            b.Speed <- rot * speed + obj.Speed
            let speedVector = b.Speed
            speedVector.Normalize()
            b.Position <- new Vector3(obj.Position.X, obj.Position.Y, 0.1f) + speedVector * 45.0f
            bullets <- b :: bullets
            shots <- obj.ID :: shots

    member internal o.RotatePlane(obj : PlaneObject, angle : float32) =
        if obj.Visible then
            let a = angle * parameters.TangageCoef * obj.Speed.Length()
            obj.Rotation <- obj.Rotation + a

    member internal o.IncrPlaneSpeed(obj : PlaneObject, d : float32) =
        if obj.Visible then obj.IncrSpeed <- obj.IncrSpeed + parameters.ThrottleCoef
